Build a Flutter Food Delivery App
A Flutter Food Delivery App is a practical project that demonstrates how to build a real-world food ordering application using Flutter and Dart. The application can include restaurant listings, food categories, menus, food details, search, cart management, address selection, checkout, order tracking, authentication, and backend/API integration.
This project combines Flutter UI development, navigation, state management, asynchronous programming, API integration, JSON handling, forms, local storage, and responsive design. Flutter supports HTTP networking through packages such as http, while application state can be shared between screens using an appropriate state-management approach. :contentReference[oaicite:0]{index=0}
1. Objectives of the Food Delivery App
- Create a complete food delivery application using Flutter.
- Display restaurants and food items.
- Organize food items into categories.
- Display restaurant menus.
- Show detailed food information.
- Implement food search and filtering.
- Add food items to a shopping cart.
- Increase and decrease food quantities.
- Calculate subtotal, delivery charges, discounts, and final total.
- Implement wishlist or favorite restaurants and foods.
- Create user login and registration screens.
- Manage delivery addresses.
- Create a checkout screen.
- Place food orders.
- Display order history and order status.
- Integrate APIs or Firebase for backend functionality.
- Handle loading, empty, success, and error states.
- Create a responsive interface for different screen sizes.
2. Technologies Used
| Technology | Purpose |
|---|
| Flutter | Build the cross-platform application interface. |
| Dart | Programming language used to develop the application. |
| Material Design | Create modern user-interface components. |
| HTTP | Communicate with REST APIs. |
| JSON | Exchange structured data between the application and server. |
| Provider | One possible approach for managing shared application state. |
| Firebase | Can provide authentication, database, storage, and other backend services. |
| SharedPreferences | Store small local preferences and application settings. |
The Flutter documentation recommends the http package as a simple way to make cross-platform HTTP requests. :contentReference[oaicite:1]{index=1}
3. Main Features
- Splash Screen
- Onboarding Screen
- Login and Registration
- Home Screen
- Restaurant Listing
- Restaurant Details
- Food Categories
- Food Menu
- Food Details
- Search Food
- Filter and Sort
- Favorite Restaurants
- Favorite Food Items
- Shopping Cart
- Quantity Management
- Delivery Address
- Checkout
- Payment Interface
- Order Confirmation
- Order History
- Order Tracking
- User Profile
- Notifications
- API Integration
4. Food Delivery App Flow
Launch App
↓
Splash Screen
↓
Login / Register
↓
Home Screen
↓
Search / Categories
↓
Restaurant Listing
↓
Restaurant Details
↓
Food Menu
↓
Food Details
↓
Add to Cart
↓
Cart
↓
Delivery Address
↓
Checkout
↓
Payment
↓
Order Confirmation
↓
Order Tracking
↓
Order History
5. Recommended Project Structure
lib/
├── main.dart
├── models/
│ ├── restaurant.dart
│ ├── food.dart
│ ├── category.dart
│ ├── cart_item.dart
│ ├── address.dart
│ └── order.dart
├── screens/
│ ├── splash_screen.dart
│ ├── login_screen.dart
│ ├── register_screen.dart
│ ├── home_screen.dart
│ ├── restaurant_screen.dart
│ ├── food_details_screen.dart
│ ├── cart_screen.dart
│ ├── checkout_screen.dart
│ ├── address_screen.dart
│ ├── orders_screen.dart
│ └── profile_screen.dart
├── services/
│ ├── restaurant_service.dart
│ ├── food_service.dart
│ ├── auth_service.dart
│ └── order_service.dart
├── providers/
│ ├── cart_provider.dart
│ ├── restaurant_provider.dart
│ ├── favorite_provider.dart
│ └── auth_provider.dart
├── widgets/
│ ├── restaurant_card.dart
│ ├── food_card.dart
│ ├── category_card.dart
│ └── cart_item_widget.dart
└── utils/
└── constants.dart
Separating models, UI screens, reusable widgets, services, and state-management classes helps keep a growing application easier to maintain.
6. Create the Flutter Project
Create a new Flutter project using the terminal:
flutter create food_delivery_app
cd food_delivery_app
flutter run
The flutter create command creates the basic Flutter project structure.
7. Add Required Packages
Packages can be added according to the features required by the project.
flutter pub add http
flutter pub add provider
flutter pub add shared_preferences
flutter pub add cached_network_image
flutter pub add url_launcher
The http package can be used for fetching and sending data to a backend API. :contentReference[oaicite:2]{index=2}
8. Create the Main Application
import 'package:flutter/material.dart';
void main() {
runApp(const FoodDeliveryApp());
}
class FoodDeliveryApp extends StatelessWidget {
const FoodDeliveryApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Food Delivery App',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.orange,
),
useMaterial3: true,
),
home: const HomeScreen(),
);
}
}
9. Designing the Home Screen
The home screen is the main area where users discover restaurants and food items. It can contain a search field, location information, food categories, promotional banners, popular restaurants, and recommended foods.
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Food Delivery'),
actions: [
IconButton(
onPressed: () {},
icon: const Icon(Icons.shopping_cart_outlined),
),
],
),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
const Text(
'What would you like to eat?',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
TextField(
decoration: InputDecoration(
hintText: 'Search restaurants or food',
prefixIcon: const Icon(Icons.search),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
],
),
);
}
}
10. Location Section
A food delivery application commonly displays the current delivery location or a selected address at the top of the home screen.
Row(
children: [
const Icon(Icons.location_on),
const SizedBox(width: 8),
const Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Deliver to',
style: TextStyle(fontSize: 12),
),
Text(
'Home Address',
style: TextStyle(
fontWeight: FontWeight.bold,
),
),
],
),
),
IconButton(
onPressed: () {},
icon: const Icon(Icons.keyboard_arrow_down),
),
],
)
11. Food Category Model
Categories allow users to quickly find food types such as Pizza, Burgers, Indian Food, Chinese Food, Desserts, and Beverages.
class FoodCategory {
final int id;
final String name;
final String image;
FoodCategory({
required this.id,
required this.name,
required this.image,
});
factory FoodCategory.fromJson(
Map json,
) {
return FoodCategory(
id: json['id'],
name: json['name'],
image: json['image'],
);
}
}
12. Food Model
class Food {
final int id;
final String name;
final String description;
final double price;
final String image;
final String category;
final double rating;
final bool isVegetarian;
Food({
required this.id,
required this.name,
required this.description,
required this.price,
required this.image,
required this.category,
required this.rating,
required this.isVegetarian,
});
factory Food.fromJson(
Map json,
) {
return Food(
id: json['id'],
name: json['name'],
description: json['description'],
price: (json['price'] as num).toDouble(),
image: json['image'],
category: json['category'],
rating: (json['rating'] as num?)?.toDouble() ?? 0,
isVegetarian: json['isVegetarian'] ?? false,
);
}
}
13. Restaurant Model
class Restaurant {
final int id;
final String name;
final String image;
final String cuisine;
final double rating;
final String deliveryTime;
final double deliveryFee;
Restaurant({
required this.id,
required this.name,
required this.image,
required this.cuisine,
required this.rating,
required this.deliveryTime,
required this.deliveryFee,
});
factory Restaurant.fromJson(
Map json,
) {
return Restaurant(
id: json['id'],
name: json['name'],
image: json['image'],
cuisine: json['cuisine'],
rating: (json['rating'] as num).toDouble(),
deliveryTime: json['deliveryTime'],
deliveryFee: (json['deliveryFee'] as num).toDouble(),
);
}
}
14. Restaurant Card
A restaurant card can display the restaurant image, name, cuisine, rating, delivery time, and delivery charge.
class RestaurantCard extends StatelessWidget {
final Restaurant restaurant;
const RestaurantCard({
super.key,
required this.restaurant,
});
@override
Widget build(BuildContext context) {
return Card(
clipBehavior: Clip.antiAlias,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Image.network(
restaurant.image,
height: 180,
width: double.infinity,
fit: BoxFit.cover,
),
Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
restaurant.name,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 6),
Text(restaurant.cuisine),
const SizedBox(height: 6),
Row(
children: [
const Icon(
Icons.star,
size: 18,
),
const SizedBox(width: 4),
Text('${restaurant.rating}'),
const Spacer(),
Text(restaurant.deliveryTime),
],
),
],
),
),
],
),
);
}
}
15. Food Card
class FoodCard extends StatelessWidget {
final Food food;
const FoodCard({
super.key,
required this.food,
});
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(10),
child: Row(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(10),
child: Image.network(
food.image,
width: 100,
height: 100,
fit: BoxFit.cover,
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
food.name,
style: const TextStyle(
fontSize: 17,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 6),
Text(
food.description,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 8),
Text(
'₹${food.price.toStringAsFixed(2)}',
style: const TextStyle(
fontWeight: FontWeight.bold,
),
),
],
),
),
],
),
),
);
}
}
16. Display Food Categories
ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: categories.length,
itemBuilder: (context, index) {
final category = categories[index];
return Padding(
padding: const EdgeInsets.only(right: 12),
child: Column(
children: [
CircleAvatar(
radius: 35,
backgroundImage: NetworkImage(
category.image,
),
),
const SizedBox(height: 6),
Text(category.name),
],
),
);
},
)
17. Restaurant Details Screen
When a user selects a restaurant, the application can display restaurant information and its complete menu.
class RestaurantDetailsScreen extends StatelessWidget {
final Restaurant restaurant;
const RestaurantDetailsScreen({
super.key,
required this.restaurant,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(restaurant.name),
),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
Image.network(
restaurant.image,
height: 220,
fit: BoxFit.cover,
),
const SizedBox(height: 16),
Text(
restaurant.name,
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Text(restaurant.cuisine),
const SizedBox(height: 8),
Text(
'⭐ ${restaurant.rating} • ${restaurant.deliveryTime}',
),
const SizedBox(height: 24),
const Text(
'Menu',
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
],
),
);
}
}
18. Food Details Screen
class FoodDetailsScreen extends StatelessWidget {
final Food food;
const FoodDetailsScreen({
super.key,
required this.food,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Food Details'),
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Image.network(
food.image,
height: 300,
width: double.infinity,
fit: BoxFit.cover,
),
const SizedBox(height: 20),
Text(
food.name,
style: const TextStyle(
fontSize: 26,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 10),
Text(
'₹${food.price.toStringAsFixed(2)}',
style: const TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
Text(food.description),
const SizedBox(height: 24),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () {},
child: const Text('Add to Cart'),
),
),
],
),
),
);
}
}
19. Navigation Between Screens
For simple application flows, Flutter's Navigator can push a new screen onto the navigation stack.
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) {
return FoodDetailsScreen(
food: food,
);
},
),
);
To return to the previous screen:
Navigator.of(context).pop();
For more advanced routing and deep-linking requirements, Flutter also supports Router-based navigation and packages such as go_router. :contentReference[oaicite:3]{index=3}
20. Shopping Cart
The cart stores the food items selected by the customer. Each cart item normally contains the selected food and quantity.
class CartItem {
final Food food;
int quantity;
CartItem({
required this.food,
this.quantity = 1,
});
double get totalPrice {
return food.price * quantity;
}
}
21. Cart Provider
A shared state-management solution can keep cart information available across different screens.
import 'package:flutter/foundation.dart';
class CartProvider extends ChangeNotifier {
final List _items = [];
List get items {
return List.unmodifiable(_items);
}
void addToCart(Food food) {
final index = _items.indexWhere(
(item) => item.food.id == food.id,
);
if (index != -1) {
_items[index].quantity++;
} else {
_items.add(
CartItem(food: food),
);
}
notifyListeners();
}
void increaseQuantity(Food food) {
final item = _items.firstWhere(
(item) => item.food.id == food.id,
);
item.quantity++;
notifyListeners();
}
void decreaseQuantity(Food food) {
final item = _items.firstWhere(
(item) => item.food.id == food.id,
);
if (item.quantity > 1) {
item.quantity--;
} else {
_items.remove(item);
}
notifyListeners();
}
void removeFromCart(Food food) {
_items.removeWhere(
(item) => item.food.id == food.id,
);
notifyListeners();
}
double get subtotal {
return _items.fold(
0,
(sum, item) => sum + item.totalPrice,
);
}
}
Flutter documentation notes that applications often need shared state between screens, and different state-management approaches can be selected according to the application's requirements. :contentReference[oaicite:4]{index=4}
22. Configure Provider
void main() {
runApp(
ChangeNotifierProvider(
create: (_) => CartProvider(),
child: const FoodDeliveryApp(),
),
);
}
23. Add Food to Cart
ElevatedButton(
onPressed: () {
context.read().addToCart(food);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Food added to cart'),
),
);
},
child: const Text('Add to Cart'),
)
24. Build Cart Screen
class CartScreen extends StatelessWidget {
const CartScreen({super.key});
@override
Widget build(BuildContext context) {
final cart = context.watch();
return Scaffold(
appBar: AppBar(
title: const Text('Your Cart'),
),
body: cart.items.isEmpty
? const Center(
child: Text('Your cart is empty'),
)
: ListView.builder(
itemCount: cart.items.length,
itemBuilder: (context, index) {
final item = cart.items[index];
return ListTile(
leading: Image.network(
item.food.image,
width: 60,
height: 60,
fit: BoxFit.cover,
),
title: Text(item.food.name),
subtitle: Text(
'₹${item.totalPrice.toStringAsFixed(2)}',
),
trailing: Text(
'Qty: ${item.quantity}',
),
);
},
),
bottomNavigationBar: Padding(
padding: const EdgeInsets.all(16),
child: Text(
'Subtotal: ₹${cart.subtotal.toStringAsFixed(2)}',
),
),
);
}
}
25. Quantity Controls
Row(
children: [
IconButton(
onPressed: () {
cart.decreaseQuantity(food);
},
icon: const Icon(Icons.remove),
),
Text('$quantity'),
IconButton(
onPressed: () {
cart.increaseQuantity(food);
},
icon: const Icon(Icons.add),
),
],
)
26. Favorite Food Items
A favorite feature allows users to save foods or restaurants for quick access later.
class FavoriteProvider extends ChangeNotifier {
final Set _favoriteFoods = {};
bool isFavorite(int foodId) {
return _favoriteFoods.contains(foodId);
}
void toggleFavorite(int foodId) {
if (_favoriteFoods.contains(foodId)) {
_favoriteFoods.remove(foodId);
} else {
_favoriteFoods.add(foodId);
}
notifyListeners();
}
}
27. Search Food
List searchFood(
List foods,
String query,
) {
if (query.trim().isEmpty) {
return foods;
}
return foods.where((food) {
return food.name
.toLowerCase()
.contains(query.toLowerCase());
}).toList();
}
28. Search Restaurants
List searchRestaurants(
List restaurants,
String query,
) {
if (query.trim().isEmpty) {
return restaurants;
}
return restaurants.where((restaurant) {
return restaurant.name
.toLowerCase()
.contains(query.toLowerCase());
}).toList();
}
29. Food Filtering
Food can be filtered by category, vegetarian preference, price range, rating, or other attributes.
List filterVegetarianFood(
List foods,
) {
return foods
.where((food) => food.isVegetarian)
.toList();
}
30. Sorting Food
Users can sort food items according to price or rating.
foods.sort(
(a, b) => a.price.compareTo(b.price),
);
For highest price first:
foods.sort(
(a, b) => b.price.compareTo(a.price),
);
31. Login Screen
final emailController = TextEditingController();
final passwordController = TextEditingController();
TextField(
controller: emailController,
keyboardType: TextInputType.emailAddress,
decoration: const InputDecoration(
labelText: 'Email',
prefixIcon: Icon(Icons.email),
),
)
TextField(
controller: passwordController,
obscureText: true,
decoration: const InputDecoration(
labelText: 'Password',
prefixIcon: Icon(Icons.lock),
),
)
32. Registration Screen
The registration form can collect information such as name, email, phone number, and password.
TextFormField(
decoration: const InputDecoration(
labelText: 'Full Name',
),
validator: (value) {
if (value == null || value.trim().isEmpty) {
return 'Name is required';
}
return null;
},
)
33. Form Validation
final formKey = GlobalKey();
Form(
key: formKey,
child: TextFormField(
validator: (value) {
if (value == null || value.trim().isEmpty) {
return 'Email is required';
}
if (!value.contains('@')) {
return 'Enter a valid email';
}
return null;
},
),
)
34. Restaurant API Integration
A backend API can provide restaurant information dynamically.
import 'dart:convert';
import 'package:http/http.dart' as http;
class RestaurantService {
Future> fetchRestaurants() async {
final response = await http.get(
Uri.parse(
'https://example.com/api/restaurants',
),
);
if (response.statusCode == 200) {
final List data = jsonDecode(response.body);
return data
.map(
(item) => Restaurant.fromJson(item),
)
.toList();
}
throw Exception(
'Failed to load restaurants',
);
}
}
Flutter's networking documentation demonstrates using http.get(), checking the response status, decoding JSON, and converting the result into application-specific Dart objects. :contentReference[oaicite:5]{index=5}
35. Food API Integration
class FoodService {
Future> fetchFoods() async {
final response = await http.get(
Uri.parse(
'https://example.com/api/foods',
),
);
if (response.statusCode != 200) {
throw Exception(
'Failed to load food items',
);
}
final List data = jsonDecode(response.body);
return data
.map(
(item) => Food.fromJson(item),
)
.toList();
}
}
36. Android Internet Permission
If the Android application communicates with an internet API, the application needs the Internet permission in its Android manifest.
This requirement is documented in Flutter's networking guide. :contentReference[oaicite:6]{index=6}
37. JSON Data Example
{
"id": 1,
"name": "Margherita Pizza",
"description": "Classic pizza with tomato and cheese",
"price": 299,
"image": "https://example.com/pizza.jpg",
"category": "Pizza",
"rating": 4.5,
"isVegetarian": true
}
JSON received from a server can be decoded with jsonDecode() and converted into Dart model objects. Flutter documents manual JSON decoding as a practical approach for smaller projects, while code generation can be useful as the number and complexity of models grows. :contentReference[oaicite:7]{index=7}
38. Loading State
Network requests can take time, so the application should display a loading indicator while restaurant or food data is being fetched.
if (isLoading) {
return const Center(
child: CircularProgressIndicator(),
);
}
39. FutureBuilder
FutureBuilder can be used to display different UI depending on whether asynchronous data is loading, available, or has produced an error. :contentReference[oaicite:8]{index=8}
FutureBuilder>(
future: foodService.fetchFoods(),
builder: (context, snapshot) {
if (snapshot.connectionState ==
ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator(),
);
}
if (snapshot.hasError) {
return Center(
child: Text(
'Error: ${snapshot.error}',
),
);
}
final foods = snapshot.data ?? [];
return ListView.builder(
itemCount: foods.length,
itemBuilder: (context, index) {
return FoodCard(
food: foods[index],
);
},
);
},
)
40. Error Handling
Network operations can fail because of connectivity issues, server errors, invalid responses, authentication failures, or malformed data.
try {
final foods = await foodService.fetchFoods();
} catch (error) {
print('Failed to load food: $error');
}
A user-friendly application should display an understandable error message and provide a retry option where appropriate.
41. Retry Button
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'Unable to load restaurants',
),
const SizedBox(height: 12),
ElevatedButton(
onPressed: () {
loadRestaurants();
},
child: const Text('Retry'),
),
],
)
42. Empty Restaurant State
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icons.restaurant_outlined,
size: 70,
),
const SizedBox(height: 16),
const Text(
'No restaurants found',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
const Text(
'Try changing your search or location.',
),
],
)
43. Empty Cart State
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icons.shopping_bag_outlined,
size: 80,
),
const SizedBox(height: 16),
const Text(
'Your cart is empty',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 12),
ElevatedButton(
onPressed: () {},
child: const Text('Browse Food'),
),
],
)
44. Delivery Address Model
class Address {
final String id;
final String name;
final String phone;
final String addressLine;
final String city;
final String postalCode;
Address({
required this.id,
required this.name,
required this.phone,
required this.addressLine,
required this.city,
required this.postalCode,
});
}
45. Address Selection
Users can select an existing delivery address or add a new address before checkout.
ListTile(
leading: const Icon(Icons.home),
title: const Text('Home'),
subtitle: const Text(
'123 Main Street, Mumbai',
),
trailing: Radio(
value: 'home',
groupValue: selectedAddress,
onChanged: (value) {
setState(() {
selectedAddress = value;
});
},
),
)
46. Checkout Screen
The checkout screen brings together the selected food items, delivery address, charges, discounts, payment option, and final order amount.
| Checkout Item | Example |
|---|
| Food Subtotal | ₹500 |
| Delivery Fee | ₹40 |
| Taxes | ₹30 |
| Discount | -₹50 |
| Final Total | ₹520 |
47. Calculate Order Total
double calculateTotal({
required double subtotal,
required double deliveryFee,
required double tax,
required double discount,
}) {
return subtotal +
deliveryFee +
tax -
discount;
}
48. Order Model
class Order {
final String id;
final List items;
final double total;
final String status;
final DateTime createdAt;
final Address deliveryAddress;
Order({
required this.id,
required this.items,
required this.total,
required this.status,
required this.createdAt,
required this.deliveryAddress,
});
}
49. Order Status
- Order Placed
- Order Confirmed
- Preparing Food
- Food Ready
- Picked Up
- Out for Delivery
- Delivered
- Cancelled
50. Order Tracking
A simple order tracking interface can display the current stage of the order.
Order Placed
↓
Confirmed
↓
Preparing
↓
Picked Up
↓
Out for Delivery
↓
Delivered
51. Order Confirmation
Scaffold(
appBar: AppBar(
title: const Text('Order Confirmed'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icons.check_circle,
size: 80,
),
const SizedBox(height: 20),
const Text(
'Your order has been placed!',
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 10),
Text(
'Order ID: $orderId',
),
],
),
),
)
52. Order History
The order history screen displays previously placed orders and their current or final status.
ListView.builder(
itemCount: orders.length,
itemBuilder: (context, index) {
final order = orders[index];
return Card(
child: ListTile(
title: Text(
'Order #${order.id}',
),
subtitle: Text(
order.status,
),
trailing: Text(
'₹${order.total.toStringAsFixed(2)}',
),
),
);
},
)
53. Payment Interface
A production food delivery application may support one or more payment methods such as cards, wallets, UPI, or cash on delivery, depending on the backend and payment provider.
| Payment Method | Example |
|---|
| Cash on Delivery | Pay when the order arrives. |
| Card | Pay using a debit or credit card. |
| UPI | Pay using a supported UPI application. |
| Wallet | Pay using a supported digital wallet. |
Sensitive payment operations should be handled through a trusted payment provider and backend rather than exposing confidential credentials in the Flutter client.
54. API Operations
| Operation | HTTP Method | Purpose |
|---|
| Get Restaurants | GET | Retrieve restaurant information. |
| Get Food Menu | GET | Retrieve food items. |
| Login | POST | Authenticate the user. |
| Register | POST | Create a new account. |
| Create Order | POST | Place a new food order. |
| Update Profile | PUT/PATCH | Update user information. |
| Update Order | PUT/PATCH | Update order status when permitted. |
| Delete Address | DELETE | Remove an address. |
55. Sending an Order to an API
Future placeOrder(
Map orderData,
) async {
final response = await http.post(
Uri.parse(
'https://example.com/api/orders',
),
headers: {
'Content-Type': 'application/json',
},
body: jsonEncode(orderData),
);
if (response.statusCode != 201) {
throw Exception(
'Failed to place order',
);
}
}
The Flutter networking cookbook demonstrates sending data with HTTP methods such as POST and PUT and encoding request data as JSON. :contentReference[oaicite:9]{index=9}
56. Authentication Flow
Open App
↓
Check Login Status
↓
Not Logged In
↓
Login / Register
↓
Authentication Successful
↓
Home Screen
↓
Browse Restaurants
↓
Select Food
↓
Cart
↓
Checkout
↓
Place Order
57. Local Storage
Small pieces of local information can be stored on the device when required. For example, the application can store selected preferences or simple state information.
import 'package:shared_preferences/shared_preferences.dart';
Future saveSelectedAddress(
String addressId,
) async {
final prefs =
await SharedPreferences.getInstance();
await prefs.setString(
'selected_address',
addressId,
);
}
Future getSelectedAddress() async {
final prefs =
await SharedPreferences.getInstance();
return prefs.getString(
'selected_address',
);
}
58. Firebase Integration
Firebase can be used as a backend option for authentication, cloud database storage, file storage, analytics, notifications, and other services.
Flutter Application
↓
Firebase Authentication
↓
Cloud Firestore
↓
Firebase Storage
Flutter's data and backend documentation includes Firebase and Firestore among the available backend topics. :contentReference[oaicite:10]{index=10}
59. Responsive Restaurant Grid
A responsive layout can change the number of restaurant columns depending on the available screen width.
LayoutBuilder(
builder: (context, constraints) {
int columns = 1;
if (constraints.maxWidth > 1000) {
columns = 3;
} else if (constraints.maxWidth > 600) {
columns = 2;
}
return GridView.builder(
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: columns,
crossAxisSpacing: 12,
mainAxisSpacing: 12,
childAspectRatio: 0.75,
),
itemCount: restaurants.length,
itemBuilder: (context, index) {
return RestaurantCard(
restaurant: restaurants[index],
);
},
);
},
)
60. Bottom Navigation
A food delivery application can provide quick access to Home, Search, Orders, Favorites, and Profile.
BottomNavigationBar(
currentIndex: selectedIndex,
onTap: (index) {
setState(() {
selectedIndex = index;
});
},
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.home_outlined),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.search),
label: 'Search',
),
BottomNavigationBarItem(
icon: Icon(Icons.receipt_long),
label: 'Orders',
),
BottomNavigationBarItem(
icon: Icon(Icons.favorite_border),
label: 'Favorites',
),
BottomNavigationBarItem(
icon: Icon(Icons.person_outline),
label: 'Profile',
),
],
)
61. Notifications
A production food delivery application can notify customers about important order events such as confirmation, food preparation, pickup, delivery, or cancellation.
- Order confirmation notification
- Food preparation notification
- Order pickup notification
- Delivery notification
- Promotional notification
62. API Loading Architecture
Flutter UI
↓
View / Screen
↓
ViewModel / Provider
↓
Service Class
↓
HTTP Request
↓
REST API
↓
JSON Response
↓
Dart Model
↓
Application State
↓
Flutter UI
Flutter's current learning materials describe MVVM as an architecture that separates the Model, View, and ViewModel responsibilities, helping make application code more reusable and testable. :contentReference[oaicite:11]{index=11}
63. MVVM Structure
Model
├── Food
├── Restaurant
├── CartItem
├── Address
└── Order
View
├── HomeScreen
├── RestaurantScreen
├── FoodDetailsScreen
├── CartScreen
└── CheckoutScreen
ViewModel
├── FoodViewModel
├── RestaurantViewModel
├── CartViewModel
└── OrderViewModel
64. Pagination
A food delivery application with a large restaurant or food catalog can use pagination so that only a limited number of records are loaded at a time.
Future> fetchRestaurants({
int page = 1,
int limit = 20,
}) async {
final uri = Uri.parse(
'https://example.com/api/restaurants'
'?page=$page&limit=$limit',
);
final response = await http.get(uri);
if (response.statusCode != 200) {
throw Exception(
'Unable to load restaurants',
);
}
final data = jsonDecode(response.body) as List;
return data
.map(
(item) => Restaurant.fromJson(item),
)
.toList();
}
65. Image Handling
Food applications rely heavily on images, so image loading should be handled carefully.
Image.network(
food.image,
width: double.infinity,
height: 220,
fit: BoxFit.cover,
errorBuilder: (
context,
error,
stackTrace,
) {
return const Icon(
Icons.image_not_supported,
size: 60,
);
},
)
66. Performance Optimization
- Use
ListView.builder for long lists.
- Use
GridView.builder for large grids.
- Optimize food and restaurant images.
- Avoid unnecessary widget rebuilds.
- Use appropriate state-management techniques.
- Use pagination for large datasets.
- Cache frequently used images when appropriate.
- Keep networking code outside UI widgets.
- Dispose controllers when they are no longer required.
- Move expensive processing away from the main UI work when appropriate.
67. Security Considerations
- Use HTTPS for production API communication.
- Do not expose confidential API or payment credentials in the Flutter application.
- Validate important order information on the backend.
- Do not trust prices calculated only on the client.
- Protect authentication tokens.
- Validate user input.
- Handle expired authentication sessions.
- Protect customer and delivery information.
68. Testing the Food Delivery App
| Testing Type | Example |
|---|
| Unit Test | Test cart and order total calculations. |
| Widget Test | Test whether food cards and buttons appear correctly. |
| Integration Test | Test the restaurant-to-checkout flow. |
| API Test | Test restaurant and food API responses. |
| Form Test | Test login, registration, and address validation. |
69. Common Problems and Solutions
| Problem | Possible Solution |
|---|
| Restaurants are not loading | Check the API URL, internet access, response status, and JSON structure. |
| Food images are broken | Check image URLs and provide an error placeholder. |
| Cart becomes empty unexpectedly | Keep cart state in an appropriate shared state-management layer and persist it if required. |
| Wrong order total | Centralize price and quantity calculations and validate important totals on the backend. |
| Login fails | Check credentials, API response, authentication state, and token handling. |
| Checkout fails | Check address, cart contents, API response, and payment/order service. |
| Application is slow | Optimize images, lists, state updates, and network operations. |
| API request fails on Android | Check Android Internet permission and network configuration. |
70. Complete Application Architecture
FOOD DELIVERY APP
|
+-----------------+-----------------+
| | |
Home Orders Profile
|
Search / Categories
|
Restaurants
|
Restaurant Details
|
Food Menu
|
Food Details
|
Add to Cart
|
Cart
|
Delivery Address
|
Checkout
|
Payment
|
Order Confirmation
|
Order Tracking
|
Order History
71. Suggested Development Steps
- Create the Flutter project.
- Configure the application theme.
- Create restaurant, food, category, cart, address, and order models.
- Design the splash and authentication screens.
- Build the home screen.
- Create food categories.
- Create restaurant cards.
- Build the restaurant details screen.
- Create food cards and food details.
- Connect the restaurant and food APIs.
- Add loading and error states.
- Create cart state management.
- Build the cart screen.
- Add quantity controls.
- Implement favorites.
- Add search and filters.
- Create login and registration.
- Build address management.
- Create the checkout screen.
- Implement order creation.
- Add order history.
- Add order tracking.
- Add local persistence where required.
- Test important application flows.
- Optimize performance.
- Prepare the application for release.
72. Best Practices
- Use meaningful names for files, classes, variables, and methods.
- Create reusable widgets.
- Separate API code from UI code.
- Use models for structured application data.
- Keep cart calculations centralized.
- Handle loading, empty, success, and error states.
- Validate user forms.
- Use responsive layouts.
- Optimize images.
- Use an appropriate state-management approach.
- Keep sensitive backend operations secure.
- Test the complete order flow.
73. Possible Future Enhancements
- Live order tracking
- GPS-based delivery location
- Restaurant reviews
- Food reviews and ratings
- Coupon codes
- Referral system
- Multiple payment methods
- Multiple delivery addresses
- Favorite restaurants
- Recommended food
- Recently ordered food
- Push notifications
- Dark mode
- Multi-language support
- Restaurant admin panel
- Delivery partner application
- Restaurant order management
- Analytics dashboard
74. Learning Outcomes
After completing this project, learners should understand how to build a practical food delivery application using Flutter. The project provides experience with Flutter widgets, Dart programming, models, API integration, JSON parsing, state management, navigation, authentication concepts, cart functionality, checkout, order management, local persistence, responsive design, error handling, and testing.
75. Interview Questions
- How would you structure a Flutter food delivery application?
- What is the purpose of a Food model?
- How do you convert JSON data into Dart objects?
- How do you fetch restaurant data from an API?
- Why are
Future, async, and await used?
- How would you manage cart state across multiple screens?
- What is
ChangeNotifier?
- How can Provider be used in a Flutter application?
- How would you implement food search?
- How would you filter vegetarian food?
- How would you calculate the final order amount?
- How would you implement order tracking?
- How would you handle API errors?
- How would you implement authentication?
- How would you make the application responsive?
- How would you optimize food images?
- What is pagination and why is it useful?
- How would you protect sensitive payment information?
- What is the difference between unit, widget, and integration testing?
76. Summary
Building a Flutter Food Delivery App is an excellent practical project for learning real-world mobile application development. The project combines restaurant discovery, food categories, menus, food details, search, filters, favorites, cart management, addresses, checkout, payments, authentication, API integration, order management, and responsive UI.
A beginner version can start with local food and restaurant data. As the project grows, API integration, authentication, shared state management, local persistence, Firebase, payment services, order tracking, and a structured architecture can be added.
Learn Flutter with JustAcademy
JustAcademy Flutter Training Course
Register for Flutter Course Demo